package pl.softech.demo2d.physics;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.LinkedList;

import pl.softech.demo2d.AbstractDemo;
import pl.softech.demo2d.physics.fluid.NSDiscretization;
import pl.softech.demo2d.physics.fluid.Particle;
import pl.softech.demo2d.physics.fluid.Simulation;
import pl.softech.demo2d.physics.fluid.grid.Cell;
import pl.softech.demo2d.physics.fluid.grid.Cell.Boundary;
import pl.softech.demo2d.physics.fluid.grid.Cell.Flag;

public class FluidDemo extends AbstractDemo {

	private static final long serialVersionUID = 1L;

	private final Simulation simulation;

	private final double gx = 0;

	private final double gy = -1;

	private final double nu = 1;

	private final double dt = 0.0001;

	private final double maxPressureDiff = 0.01;

	private double dx;
	private double dy;

	private double height, width;

	public FluidDemo(Dimension dim, int nx, int ny) {
		super(dim);
		this.simulation = createSimulation(dim.width, dim.height, nx, ny);
		this.setBackground(Color.WHITE);
		 this.sleep = 1;
	}

	private Simulation createSimulation(int width, int height, int nx, int ny) {

		Cell[][] cells = new Cell[nx][ny];
		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[i].length; j++) {
				cells[i][j] = new Cell(cells, i, j);
			}
		}

		this.width = width;
		this.height = height;
		dx = (double) width / cells.length;
		dy = (double) height / cells[0].length;

		NSDiscretization nsDiscretization = new NSDiscretization(dx, dy, dt, gx, gy, nu);

		addBoundaries(cells);
		Collection<Particle> particles = createParticles(cells, dx, dy);

		return new Simulation(cells, particles, nsDiscretization, maxPressureDiff);
	}

	private Collection<Particle> createParticles(Cell[][] cells, double dx, double dy) {

		Collection<Particle> particles = new LinkedList<Particle>();

		double x = dx / 2;
		double y = 0;
		for (int i = 0; i < cells.length; i++) {
			y = dy / 2;
			for (int j = 0; j < cells[i].length / 2; j++) {

				if (cells[i][j].flag != Flag.BOUNDARY) {

					particles.add(new Particle(x, y, i, j));

				}

				y += dy;

			}
			x += dx;
		}

		double x0 = width / 2;
		double y0 = 1.5 * height / 2;
		double r = width / 8;

		for (x = -r; x < x0 + r; x += dx) {
			for (y = -r; y < y0 + r; y += dy) {

				if (Math.pow(x0 - x, 2) + Math.pow(y0 - y, 2) < r * r) {
					int i = (int) (x / dx);
					int j = (int) (y / dy);
					particles.add(new Particle(x, y, i, j));
				}

			}
		}

		return particles;
	}

	private void addBoundaries(Cell[][] cells) {

		int maxI = cells.length - 1;
		int maxJ = cells[0].length - 1;

		for (int i = 0; i < cells.length; i++) {

			cells[i][0].flag = Flag.BOUNDARY;
			cells[i][0].boundary = Boundary.FREE_SLIP;

			cells[i][maxJ].flag = Flag.BOUNDARY;
			cells[i][maxJ].boundary = Boundary.FREE_SLIP;

		}

		for (int j = 0; j < cells[0].length; j++) {

			cells[0][j].flag = Flag.BOUNDARY;
			cells[0][j].boundary = Boundary.FREE_SLIP;

			cells[maxI][j].flag = Flag.BOUNDARY;
			cells[maxI][j].boundary = Boundary.FREE_SLIP;
		}

	}

	@Override
	protected void doPaint(Graphics2D g2) {

		Cell[][] cells = simulation.getCells();

		double x = 0;
		double y = 0;

		g2.setColor(Color.BLACK);
		for (int i = 0; i < cells.length; i++) {
			y = 0;
			for (int j = cells[i].length - 1; j >= 0; j--) {

				Rectangle2D rec = new Rectangle2D.Double(x, y, dx, dy);

				if (cells[i][j].flag == Flag.BOUNDARY) {

					g2.setColor(Color.BLACK);
					g2.fill(rec);

				}

				g2.setColor(Color.GREEN);
				g2.draw(rec);

				y += dy;

			}
			x += dx;
		}

		g2.setColor(Color.BLUE);
		for (Particle p : simulation.getParticles()) {

			Rectangle2D rec = new Rectangle2D.Double(p.x - dx / 4, height - p.y - dy / 4, dx / 2, dy / 2);
			g2.fill(rec);

		}

	}

	@Override
	protected void doUpdate(long elapsed) {
		simulation.update(dt);
	}

	public static void main(String[] args) {
		Dimension dim = new Dimension(800, 800);
		show(new FluidDemo(dim, 50, 50), new Dimension(900, 900));
		// show(new FluidDemo(dim, 10, 10), new Dimension(900, 900));
	}

}
